/*
 * @(#) $Header$
 *
 * Copyright (C) 2010  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.crypto.md;

/**
 * Class {@code BigSHA} groups together the SHA digest that use {@code long}.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.crypto.md.BigSHA">Daniel Léonard</a>
 * @version $Revision$
 */
public abstract class BigSHA extends SHAFamily {

//----------------------------
// Instance variable
//----------------------------

   /** 8 words buffer (H1, H2, H3, H4, H5, H6, H7, H8). */
   private long[] H;

   /** 80 word buffer buffer. */
   private long[] W;

   /** Most significant bits of the 128 bit counter. */
   private long most = 0L;
   /** Least significant bits of the 128 bit counter. */
   private long less = 0L;
   /** Overflow toggle for the least significant bits. */
   private boolean positive = true;


//----------------------------
// Scrambling methods
//----------------------------

   private static long Ch(long x, long y, long z) {
      return ((x & y) ^ (~x & z));
      }

   private static long Maj(long x, long y, long z) {
      return ((x & y) ^ (x & z) ^ (y & z));
      }

   private static long SIGMA0(long x) {
      return (AbstractBlockMessageDigest.rotateRight(x, 28) ^ AbstractBlockMessageDigest.rotateRight(x, 34) ^ AbstractBlockMessageDigest.rotateRight(x, 39));
      }

   private static long SIGMA1(long x) {
      return (AbstractBlockMessageDigest.rotateRight(x, 14) ^ AbstractBlockMessageDigest.rotateRight(x, 18) ^ AbstractBlockMessageDigest.rotateRight(x, 41));
      }

   private static long sigma0(long x) {
      return (AbstractBlockMessageDigest.rotateRight(x, 1) ^ AbstractBlockMessageDigest.rotateRight(x, 8) ^ (x >>> 7));
      }

   private static long sigma1(long x) {
      return (AbstractBlockMessageDigest.rotateRight(x, 19) ^ AbstractBlockMessageDigest.rotateRight(x, 61) ^ (x >>> 6));
      }


//---------------------------
// Constructor
//---------------------------

   /**
    * Constructor.
    * @param   name   the name of the algorithm
    * @param   buffer_len   the size of one block in bytes.
    * @param   digest_len   the size of the digest in bytes.
    * @param   h_len   the size of the H buffer in blocks of 32 bits.
    * @param   w_len   the size of the W buffer in blocks of 32 bits.
    */
   public BigSHA(String name, int buffer_len, int digest_len, int h_len, int w_len) {
      super(name, buffer_len, digest_len);

      long[] h = new long[h_len];
      this.setHBuffer(h);

      long[] w = new long[w_len];
      this.setWBuffer(w);
      }


//---------------------------
// Accessors and mutators
//---------------------------

     /**
      * Completely changes the W buffer.
      * @param   state   the new W buffer.
      */
     protected void setWBuffer(long[] state) {
        this.W = state;
        }

     /**
      * Gets the W buffer.
      * @return   the W buffer.
      */
     protected long[] getWBuffer() {
        return this.W;
        }

    /**
     * Completely changes the H buffer.
     * @param   state   the new H buffer.
     */
     protected void setHBuffer(long[] state) {
        this.H = state;
        }

     /**
      * Gets the H buffer.
      * @return   the H buffer.
      */
     protected long[] getHBuffer() {
        return this.H;
        }


//---------------------------
// Instance method
//---------------------------

   /**
    * Increments the 128-bit counter.
    * @param   amount   the amount to increment.
    */
   protected void increment(int amount) {
   // 128 bits addition
      this.less += amount * 8L;
      if (this.less < 0L) {
         this.positive = false;
         }
      if ((this.less >= 0L) && (this.positive == false)) {
         this.most++;
         this.positive = true;
         }
      }

   /**
    * Resets the 128-bit counter.
    */
   protected void resetCount() {
      this.less = 0L;
      this.most = 0L;
      this.positive = true;
      }


//---------------------------
// Abstract method
//---------------------------

   /**
    * Puts the magic numbers into the H buffer.
    */
   protected abstract void resetHBuffer();

   /**
    * Gets the magic number array K.
    * @return   the magic number array K.
    */
   protected abstract long[] getKArray();


//---------------------------
// Implemented methods from fork.crypto.AbstractBlockMessageDigest
//---------------------------

   @Override
   protected void coreInit() {
      this.resetHBuffer();

      this.resetCount();

      long[] w = this.getWBuffer();
      AbstractBlockMessageDigest.zeroize(w);
      }

   @Override
   @SuppressWarnings("hiding")
   protected void coreTransform() {
      this.increment(SHA512.BUFFER_LEN);

      byte[] buffer = this.getBuffer();
      long[] w = this.getWBuffer();
      w[ 0] = AbstractBlockMessageDigest.makeLong(buffer,   0,   1,   2,   3,   4,   5,   6,   7);
      w[ 1] = AbstractBlockMessageDigest.makeLong(buffer,   8,   9,  10,  11,  12,  13,  14,  15);
      w[ 2] = AbstractBlockMessageDigest.makeLong(buffer,  16,  17,  18,  19,  20,  21,  22,  23);
      w[ 3] = AbstractBlockMessageDigest.makeLong(buffer,  24,  25,  26,  27,  28,  29,  30,  31);
      w[ 4] = AbstractBlockMessageDigest.makeLong(buffer,  32,  33,  34,  35,  36,  37,  38,  39);
      w[ 5] = AbstractBlockMessageDigest.makeLong(buffer,  40,  41,  42,  43,  44,  45,  46,  47);
      w[ 6] = AbstractBlockMessageDigest.makeLong(buffer,  48,  49,  50,  51,  52,  53,  54,  55);
      w[ 7] = AbstractBlockMessageDigest.makeLong(buffer,  56,  57,  58,  59,  60,  61,  62,  63);
      w[ 8] = AbstractBlockMessageDigest.makeLong(buffer,  64,  65,  66,  67,  68,  69,  70,  71);
      w[ 9] = AbstractBlockMessageDigest.makeLong(buffer,  72,  73,  74,  75,  76,  77,  78,  79);
      w[10] = AbstractBlockMessageDigest.makeLong(buffer,  80,  81,  82,  83,  84,  85,  86,  87);
      w[11] = AbstractBlockMessageDigest.makeLong(buffer,  88,  89,  90,  91,  92,  93,  94,  95);
      w[12] = AbstractBlockMessageDigest.makeLong(buffer,  96,  97,  98,  99, 100, 101, 102, 103);
      w[13] = AbstractBlockMessageDigest.makeLong(buffer, 104, 105, 106, 107, 108, 109, 110, 111);
      w[14] = AbstractBlockMessageDigest.makeLong(buffer, 112, 113, 114, 115, 116, 117, 118, 119);
      w[15] = AbstractBlockMessageDigest.makeLong(buffer, 120, 121, 122, 123, 124, 125, 126, 127);
      for (int i = 16; i < 80; i++) {
         w[i] = BigSHA.sigma1(w[i - 2]) + w[i - 7] + BigSHA.sigma0(w[i - 15]) + w[i - 16];
         }

      AbstractBlockMessageDigest.zeroize(buffer);

      long[] H = this.getHBuffer();
      long a = H[0];
      long b = H[1];
      long c = H[2];
      long d = H[3];
      long e = H[4];
      long f = H[5];
      long g = H[6];
      long h = H[7];
      long T1 = 0; long T2 = 0;
      long[] k = this.getKArray();
      for (int i = 0; i < 80; i++) {
         T1 = h + BigSHA.SIGMA1(e) + BigSHA.Ch(e, f, g) + k[i] + w[i];
         T2 = BigSHA.SIGMA0(a) + BigSHA.Maj(a, b, c);
         h = g;
         g = f;
         f = e;
         e = d + T1;
         d = c;
         c = b;
         b = a;
         a = T1 + T2;
         }

      AbstractBlockMessageDigest.zeroize(w);

      H[0] += a;
      H[1] += b;
      H[2] += c;
      H[3] += d;
      H[4] += e;
      H[5] += f;
      H[6] += g;
      H[7] += h;
      }

   @Override
   protected byte[] coreDoFinal() {
// count is used here, it does not matter if it overflowed, we only need its
// value mod 128
      long count = this.getCount();
      int buf_len = this.getBufferLen();
      int index = (int) (count % buf_len);
// we then add index * 8 to the counter, that is what is left of unprocessed
// data
      this.increment(index);

      int padlen = (index < (buf_len - 16)) ? (buf_len - 16) - index : (2 * buf_len - 16) - index;
      byte[] last = new byte[padlen + 16];

      last[0] = (byte) 0x80;
      padlen = AbstractBlockMessageDigest.breakLongBE(this.most, last, padlen);
      padlen = AbstractBlockMessageDigest.breakLongBE(this.less, last, padlen);

      this.engineUpdate(last, 0, padlen);

      int digest_len = this.getDigestLen();
      byte[] digest = new byte[digest_len];
      long[] h = this.getHBuffer();
      for (int i = 0, j = 0; j < digest_len; i++) {
         j = AbstractBlockMessageDigest.breakLongBE(h[i], digest, j);
         }

      this.coreInit();
      return digest;
      }


//---------------------------
// Implemented methods from java.lang.Cloneable
//---------------------------

    /**
     * Makes a deep copy this message digest in its current state.
     * @return   a clone of this message digest instance.
     */
   @Override
   public Object clone() throws CloneNotSupportedException {
       BigSHA sha = (BigSHA) super.clone();
       sha.H = this.H.clone();
       sha.W = this.W.clone();
       return sha;
       }

   }

